package com.pxx.pj.common.aspect;

import com.pxx.pj.common.annotation.RequiredLog;
import com.pxx.pj.common.util.IPUtils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pxx.pj.pxx.pojo.PxxLog;
import com.pxx.pj.pxx.pojo.PxxUser;
import com.pxx.pj.pxx.service.PxxLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * @Aspect 注解描述的对象为一个切面对象,在切面对象中定义
 * 1)切入点(Pointcut):织入扩展功能的一些连接点的集合
 * 2)通知方法(Advice):封装了扩展逻辑的方法
 */

//@Order(1)
@Slf4j
@Aspect
@Component
public class PxxLogAspect {
       //通过Pointcut定义一个切入点,@annotation方式为定义切入点的一种方式,
       //在这里表示业务对象中由com.cy.pj.common.annotation.RequiredLog注解描述的方法为一些切入点方法
       @Pointcut("@annotation(com.pxx.pj.common.annotation.RequiredLog)")
       public void doLog(){}//doLog方法仅仅是@Pointcut注解的一个载体,方法体内不需要写任何内容

/**
     * @Around 注解描述的方法可以在目标方法执行之前和之后做功能扩展
     * @param joinPoint 封装了目标方法信息的一个对象(连接点对象)
     * @return 目标方法的执行结果
     * @throws Throwable
     */

       @Around("doLog()")
       public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{

            try {
               long t1 = System.currentTimeMillis();
               Object result = joinPoint.proceed();//去调用目标方法,其返回值为目标方法返回值
               long t2 = System.currentTimeMillis();
               System.out.println("time:" + (t2 - t1));
               //将正常的用户行为日志写入到数据库
               saveSysLog(joinPoint, (t2 - t1));
               return result;
           }catch(Throwable e){
               //saveErrorLog(...);也可以将错误日志写入到数据库
               logError(joinPoint,e.getMessage());
               throw e;
           }
       }
       //将错误日志进行输出并记录
       private void logError(ProceedingJoinPoint joinPoint,String exceptionMsg) throws JsonProcessingException {
           String targetClassName=joinPoint.getTarget().getClass().getName();
           String methodName=joinPoint.getSignature().getName();
           String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
           log.error("error.msg->{}->{}->{}",targetClassName+"."+methodName,params,exceptionMsg);
       }

       @Autowired
       private PxxLogService sysLogService;

       private void saveSysLog(ProceedingJoinPoint joinPoint,long time) throws NoSuchMethodException, JsonProcessingException {
           //1.获取用户行为日志
           //获取目标对象类型
           Class<?> targetClass=joinPoint.getTarget().getClass();
           //获取目标方法的签名信息
           MethodSignature ms=(MethodSignature) joinPoint.getSignature();
           //获取目标方法?(类中方法的唯一标识是什么:方法名+参数列表)
           Method targetMethod=targetClass.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
           //获得RequiredLog
           RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
           //获取操作名(RequiredLog中operation的值)
           String operation= requiredLog.value();
           //2.封装日志信息
           PxxLog entity=new PxxLog();
           PxxUser user=(PxxUser) SecurityUtils.getSubject().getPrincipal();
           entity.setUsername(user.getUsername());//将来这个位置为登录用户名
           entity.setIp(IPUtils.getIpAddr());
           entity.setOperation(operation);//为目标方法指定的一个名字
           entity.setMethod(targetClass.getName()+"."+targetMethod.getName());//类全名+方法名
           //entity.setParams(Arrays.toString(joinPoint.getArgs()));//调用方法时传递实际参数
           entity.setParams(new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
           entity.setTime(time);
           entity.setCreatedTime(new Date());
           //3.保存用户行为值
           sysLogService.saveObject(entity);
           //异步写日志(自己new thread,借助池中线程,但非tomcat线程池中线程)
//           new Thread(){//1M
//               @Override
//               public void run() {
//                   sysLogService.saveObject(entity);
//               }
//           }.start();
       }
}
